<?php
exit;
/**
 * Server daemon
 *
 * Events:
 * - onTick [float $deltaTime]
 * - onServerTick [float $deltaTime]
 * - onServerLogFinish [float $deltaTime]
 *
 * Copyright (c) 2011, EpicLegion
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @author     EpicLegion
 * @package    cod_daemon
 * @subpackage core
 * @license    http://www.opensource.org/licenses/bsd-license.php New BSD License
 */

// Root path
define('ROOT_PATH', dirname(__FILE__).'/');

// Debugging?
define('DEBUG', TRUE);

// Functions
require_once ROOT_PATH.'lib/functions.php';

// Instance resume support
if (isset($_GET['id']) AND !empty($_GET['id']))
{
    define('INSTANCE_ID'    , $_GET['id']);
    define('INSTANCE_RESUME', TRUE);
}
else
{
    define('INSTANCE_ID'    , sha1(uniqid(mt_rand(), TRUE)));
    define('INSTANCE_RESUME', FALSE);

    if (is_file(ROOT_PATH.'tmp/run.lock'))
    {
        cli_print('Script is locked. Please stop/resume previous instance OR remove tmp/run.lock file');
        exit;
    }

    file_put_contents(ROOT_PATH.'tmp/run.lock', INSTANCE_ID.':0');
}

// No errors
if (!DEBUG) error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);

// Connect to database
cli_print('Connecting to database...');

if (Config::getCoreConfig('database.type', 'mysql') == 'pgsql')
{
    require_once ROOT_PATH.'lib/classes/DatabasePgsql.php';
}

Database::instance(Config::getCoreConfig('database', array()));

// Important consts
define('SECONDS_PER_TICK', Config::getCoreConfig('main.seconds_per_tick', 3));
define('MAX_SECONDS'     , Config::getCoreConfig('main.max_seconds', 0));

// Shutdown handler
register_shutdown_function('daemon_shutdown_handler');

// Runtime and commands
Commands::init();
Runtime::init();
Warning::init();

// Initialize core managers
if (INSTANCE_RESUME)
{
    ServerManager::resume(Runtime::get('servers', array()));
    PluginManager::resume(Runtime::get('plugins', ''));
    PlayerManager::resume(Runtime::get('players', array()));
    VoteManager::resume(Runtime::get('votes', array()));
}
else
{
    ServerManager::init();
    PluginManager::init();
    PlayerManager::init();
    VoteManager::init();
}

// Time
$deltaTime = Runtime::get('delta-time', array());

if (empty($deltaTime))
{
    $deltaTime = array('tick' => microtime(TRUE), 'server' => microtime(TRUE), 'log' => microtime(TRUE));
}

// Parser
$logParser = new LogParser(Runtime::get('timestamps', array()));

// Start of execution time
define('START_TIME', time());

// Application loop
while (TRUE)
{
    // Clear cache
    clearstatcache();

    // Read instance
    if (is_file(ROOT_PATH.'tmp/run.lock'))
    {
        // Read file
        $runLock = file_get_contents(ROOT_PATH.'tmp/run.lock');

        // Valid?
        if ($runLock !== FALSE AND strstr($runLock, ':'))
        {
            // Validate content
            $runLock = explode(':', $runLock, 2);

            if ($runLock[0] != INSTANCE_ID OR !ctype_digit($runLock[1]))
            {
                break;
            }

            $runLock[1] = (int) $runLock[1];

            // Wait?
            if ($runLock[1] > time())
            {
                // Sleep
                sleep($runLock[1] - time());

                // Free memory
                unset($runLock);

                // Next iteration
                continue;
            }

            // Free memory
            unset($runLock);
        }
        else
        {
            break;
        }
    }
    else
    {
        // Stop execution
        break;
    }

    // Tick start
    Event::fireEvent('onTick', array(microtime(TRUE) - $deltaTime['tick']));
    $deltaTime['tick'] = microtime(TRUE);

    // Init data for fetching logs
    $curlData = array();
    $multiHandle = curl_multi_init();
    $i = 0;

    // Iterate servers
    foreach (ServerManager::get() as $server)
    {
        // Set current server and config group
        Config::$defaultFile = 'server-'.$server->id;
        Server::$current = $server;
        Server::$language = Config::get('rcon.language', 'en');

        // Server tick
        Event::fireEvent('onServerTick', array(microtime(TRUE) - $deltaTime['server']));
        $deltaTime['server'] = microtime(TRUE);

        // Logging server
        if (!empty($server->logURL))
        {
            // Setup array
            $curlData[$i] = array('server' => $server, 'logs' => '', 'handle' => curl_init());

            // Settings
            curl_setopt($curlData[$i]['handle'], CURLOPT_URL           , $server->logURL);
            curl_setopt($curlData[$i]['handle'], CURLOPT_FRESH_CONNECT , TRUE); // Not sure about that one
            curl_setopt($curlData[$i]['handle'], CURLOPT_RETURNTRANSFER, TRUE);
            curl_setopt($curlData[$i]['handle'], CURLOPT_TIMEOUT       , 3);
            curl_setopt($curlData[$i]['handle'], CURLOPT_USERAGENT     , 'Web Rcon 1.x');
            curl_setopt($curlData[$i]['handle'], CURLOPT_RANGE         , -20000); // Last 20kbytes. Thanks slydog

            // Multihandle
            curl_multi_add_handle($multiHandle, $curlData[$i]['handle']);

            // Next number
            $i++;
        }
    }

    // Execute cURL
    if (!empty($curlData))
    {
        // Execute multihandle
        $running = NULL;

        // As long as it's doing something
        do
        {
            curl_multi_exec($multiHandle, $running);
        }
        while ($running > 0);

        // Get contents
        for ($j = 0; $j < $i; $j++)
        {
            // Get content
            $curlData[$j]['logs'] = curl_multi_getcontent($curlData[$j]['handle']);

            // Set current server and config group
            Config::$defaultFile = 'server-'.$curlData[$j]['server']->id;
            Server::$current = $curlData[$j]['server'];

            // Wipe history
            Server::$current->wipeHistory();

            // Parse log
            $logParser->parseLogFile($curlData[$j]['logs']);

            // Event
            Event::fireEvent('onServerLogFinish', array(microtime(TRUE) - $deltaTime['log']));
            $deltaTime['log'] = microtime(TRUE);

            // Close
            curl_multi_remove_handle($multiHandle, $curlData[$j]['handle']);
        }

        // Memory
        unset($running, $j);
    }

    // Clean things up
    curl_multi_close($multiHandle);
    unset($curlData, $i, $multiHandle);

    // Write tick data
    file_put_contents(ROOT_PATH.'tmp/run.lock', INSTANCE_ID.':'.(time() + SECONDS_PER_TICK));

    // Resume
    if (MAX_SECONDS AND !SCRIPT_CLI AND ((time() - START_TIME) >= MAX_SECONDS))
    {
        // Runtime
        Runtime::set('delta-time', $deltaTime);
        Runtime::set('servers', ServerManager::get());
        Runtime::set('plugins', serialize(PluginManager::get()));
        Runtime::set('players', PlayerManager::getPlayers(TRUE));
        Runtime::set('votes', VoteManager::get());
        Runtime::set('timestamps', $logParser->getTimestamps());
        Runtime::save();

        // Init cURL
        $curl = curl_init();

        // Settings
        curl_setopt($curl, CURLOPT_URL           , $_SERVER['SERVER_NAME'].$_SERVER['SCRIPT_NAME'].'?id='.INSTANCE_ID);
        curl_setopt($curl, CURLOPT_FRESH_CONNECT , TRUE); // Not sure about that one
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($curl, CURLOPT_TIMEOUT       , 1);

        // Execute
        curl_exec($curl);

        // Avoid cleanup
        define('AVOID_CLEANUP', TRUE);

        // Stop
        break;
    }
}